package br.srv.full.virtual.faces.manager.userLogin;


import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.swing.event.EventListenerList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import br.srv.full.faces.utils.FacesMessageUtils;
import br.srv.full.faces.utils.FacesUtils;
import br.srv.full.serviceLocator.ServiceLocator;
import br.srv.full.virtual.engine.users.FirstLoginException;
import br.srv.full.virtual.engine.users.PersonsManager;
import br.srv.full.virtual.engine.users.PersonsManagerException;
import br.srv.full.virtual.entities.auth.Login;
import br.srv.full.virtual.entities.auth.User;
import br.srv.full.virtual.entities.resume.UserLogin;
import br.srv.full.virtual.faces.manager.Action;
import br.srv.full.zipper.jsf.event.ZipperEventListener;

/**
 * @author Delfino
 * 
 */
public class LoginManager {

	/**
	 * @author Delfino
	 * 
	 */
	public enum ActionLoginManager implements Action {
		/**
		 * 
		 */
		confirm,
		/**
		 * 
		 */
		confirmed,
		/**
		 * 
		 */
		canceled,
		/**
		 * 
		 */
		startProtected,
		/**
		 * 
		 */
		firstAccessSuccess,
		/**
		 * 
		 */
		changePasswordSucess,
		/**
		 * 
		 */
		failSendMessage,
		/**
		 * 
		 */
		firstFaseRegistration,
		/**
		 * 
		 */
		endFirstFaseRegistration,
		/**
		 * 
		 */
		firstAcessChangePassword,
		/**
		 * 
		 */
		loginSucess, logoff;

		private final Log log = LogFactory.getLog(getClass());

		public String toString() {

			String string = super.toString();
			log.info(string);
			return string;
		}
	}

	private final Log log = LogFactory.getLog(getClass());

	private UserLogin userLogin;

	/*
	 * Armazenará o UUID para a passagem da primeira para segunda fase.
	 */
	private String firstFaseCode;

	/*
	 * Armazenará a senha para verificação no cadastro de nova ou troca da
	 * senha.
	 */
	private String verifyPasswd;

	/*
	 * Armazenará a senha antiga quando da troca de senha.
	 */
	private String passwd;

	/**
	 * @param _firstFaseCode
	 */
	public void setFirstFaseCode(String _firstFaseCode) {
		log.info("UUID :" + _firstFaseCode);
		this.firstFaseCode = _firstFaseCode;
	}

	/**
	 * 
	 */
	@PostConstruct
	public void initialize() {
		// TODO Auto-generated method stub
		log.info("Inicializando");
	}

	private String lastErrorMessage;

	private UserLogin logoffUser = new UserLogin();

	private final EventListenerList eventListenerList = new EventListenerList();

	/**
	 * @return
	 */
	public String getLastErrorMessage() {
		return lastErrorMessage;
	}

	/**
	 * Confirma o registro do usuário consultando se já existe um e-mail e Aka.
	 * 
	 * Caso não exista salva o novo usuário e envia uma mensagem com o UUID para
	 * segunda fase. Se existir retorna a pagina original e apresenta as
	 * mensagens criadas na consulta ({@link #consultIfUserExist()})
	 * 
	 * @return Action a ser tomada
	 * @see Action
	 * @see ActionLoginManager
	 */
	public Action confirmRegistration() {
		log.info("Confirmando o registro do usuário");
		if (consultIfUserExist()) {
			return null;
		}
		forceUserLogin(getPersonssManager().confirmRegistration(getUserLogin()));

		return ActionLoginManager.endFirstFaseRegistration;

	}

	private boolean consultIfUserExist() {

		boolean l_userExist = false;
		l_userExist = getPersonssManager().consultIfEmailExist(getUserLogin());
		if (l_userExist) {
			log.info("Já existe o e-mail " + getUserLogin().getEmail() + " cadastrado!");
			FacesMessage l_message = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_INFO,
					"Já temos um cadastrado utilizanod este e-mail!", "Já temos um cadastrado utilizando este e-mail, "
							+ "por favor digirija-se a pagina de FAQ e veja como proceder!");
			FacesUtils.addMessage("tfUserEmail", l_message);
		}
		return l_userExist;
	}

	/**
	 * @return
	 */
	public Action cancelRegistration() {
		log.info("Cancelando Registro em andamento!");
		return ActionLoginManager.canceled;
	}

	/**
	 * 
	 */
	@PreDestroy
	public void destroy() {
		// TODO Auto-generated method stub
		log.info("Destruindo");
	}

	/**
	 * @return
	 */
	public UserLogin getUserLogin() {
		return userLogin;
	}

	/**
	 * @param user
	 */
	public void setUserLogin(UserLogin user) {
		this.userLogin = user;
	}

	/**
	 * @return
	 */
	public Action changePassword() {
		if (getUserLogin().isFirstAccess()) {
			return changeFirstPassword();
		} else {
			return changeOldPassword();
		}
	}

	/**
	 * 
	 */
	private synchronized Action changeFirstPassword() {
		UserLogin l_userLogin = getUserLogin();
		if (getPasswd() == null || (!getPasswd().equals(getVerifyPasswd()))) {
			FacesMessage l_msg = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, "As senhas não confere!",
					"As senhas não confere, " + "as senhas digitadas nos campos devem ser identicas!");
			FacesUtils.addMessage("password", l_msg);

			return null;
		}
		try {

			PersonsManager l_personssManager = getPersonssManager();
			
			l_userLogin = l_personssManager.createFirstLogin(l_userLogin);
			l_userLogin = l_personssManager.changePassword(l_userLogin, getPasswd());

			l_personssManager.update(l_userLogin);
			l_userLogin = l_personssManager.increaseAccessCount(l_userLogin);

			forceUserLogin(l_userLogin);
		} catch (FirstLoginException e) {
			e.printStackTrace();
			log.warn("Problemas ao Criar o primeiro Login", e);
			return null;

		} catch (PersonsManagerException e) {
			e.printStackTrace();
			log.warn(e);
			return null;
		}
		return ActionLoginManager.firstAccessSuccess;
	}

	/**
	 * @param p_userLogin
	 */
	private void forceUserLogin(UserLogin p_userLogin) {
		FacesUtils.setSessionMapValue("userLogin", p_userLogin);
		setUserLogin(p_userLogin);
	}

	/**
	 * @return
	 */
	private Action changeOldPassword() {
		UserLogin l_userLogin = getUserLogin();
		if (l_userLogin.getPassword() == null || (!l_userLogin.getPassword().equals(getVerifyPasswd()))) {
			FacesMessage l_msg = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, "As senhas não confere!",
					"As senhas não confere, " + "as senhas digitadas nos campos devem ser identicas!");
			FacesUtils.addMessage("password", l_msg);

			return null;
		}
		try {

			l_userLogin = getPersonssManager().changePassword(l_userLogin, getPasswd());
			forceUserLogin(l_userLogin);
		} catch (PersonsManagerException e) {
			e.printStackTrace();
			log.warn(e);
			return null;
		}
		return ActionLoginManager.changePasswordSucess;
	}

	/**
	 * @return
	 */
	public Action goToSecondStep() {
		// Obtem o codigo de registro do usuário, este codigo foi inserido no
		// LoginManager quando a seção foi iniciada na pagina
		// SecondStepRegistration.faces ou quando a pagina
		// endFirstFaseRegistration chamou esta ação
		try {
			log.trace("Buscando o UUID");
			List<UserLogin> l_userLoginList = getPersonssManager().findUserLoginByUUID(getFirstFaseCode());

			if (l_userLoginList == null || l_userLoginList.isEmpty()) {
				throw new RuntimeException(
						"Verifique os processo e/ou o registro do usuário, pois o UUID informado está sem o primeiro login padráo!");
			}
			if (l_userLoginList.size() > 1) {
				throw new RuntimeException(
						"Verifque os processo este passo so deve ser executado, quando o usu[ario estiver sendo cadastrado, este usuário tem "
								+ l_userLoginList.size() + " login!");
			}
			UserLogin l_userLogin = l_userLoginList.get(0);
			if (!l_userLogin.isFirstAccess()) {
				FacesMessageUtils.addMessage("UUID", FacesMessage.SEVERITY_ERROR,
						"UUID já finalziado processo inicial de cadastro!",
						"Este UUID já teve seu processo inicial de castro finalizado, verifque na seção FAQ!");
				return null;
			}
			forceUserLogin(l_userLogin);

			return ActionLoginManager.firstAcessChangePassword;
		} catch (Exception _e) {
			log.warn(_e);
			// registra nas mensagens os erros
			// qualquer problema retorna para a página que chamou a ação.
			return null;
		}
	}

	/**
	 * @return
	 */
	public boolean isFirstAcess() {
		return getUserLogin().getPassword() == null;
	}

	/**
	 * @param lastErrorMessage
	 */
	public void setLastErrorMessage(String lastErrorMessage) {
		this.lastErrorMessage = lastErrorMessage;
	}

	/**
	 * @return
	 */
	public String getFirstFaseCode() {
		return firstFaseCode;
	}

	public String getVerifyPasswd() {
		return verifyPasswd;
	}

	public void setVerifyPasswd(String _verifyPasswd) {
		verifyPasswd = _verifyPasswd;
	}

	public String getPasswd() {
		return passwd;
	}

	public void setPasswd(String _oldPasswd) {
		passwd = _oldPasswd;
	}

	public Action makeLogin() {

		UserLogin l_userLogin = getUserLogin();
		// Obtem o codigo de registro do usuário, este codigo foi inserido no
		// LoginManager quando a seção foi iniciada na pagina
		// SecondStepRegistration.faces ou quando a pagina
		// endFirstFaseRegistration chamou esta ação
		try {
			UserLogin l_atualUser = l_userLogin;
			PersonsManager l_usersManager = getPersonssManager();
			List<UserLogin> l_userLoginList = l_usersManager.findUserLogin(Login.FIND_BY_LOGIN, l_atualUser
					.getUserLogin());

			if (l_userLoginList == null || l_userLoginList.isEmpty() || l_userLoginList.size() > 1) {
				throw new RuntimeException("Problema no processo, Login entrado: " + l_userLoginList);
			} else {
				l_userLogin = l_userLoginList.get(0);
			}

			if (l_userLogin == null) {
				FacesMessage l_msg = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_ERROR,
						"Falha ao Efetuar o Login!", "Falha ao Efetuar o Login, " + "Verifique o login usado!");
				FacesUtils.addMessage("j_username", l_msg);
				FacesUtils.addMessage("j_password", l_msg);
				return null;
			} else if (l_userLogin.getPassword() == null) {
				FacesMessage l_msg = FacesMessageUtils
						.getMessage(
								FacesMessage.SEVERITY_ERROR,
								"Falha ao Efetuar o Login!",
								"Falha ao Efetuar o Login, "
										+ "Verifique o login usado, se já foi cadastrado certifique que foi confirmado e cadastrado uma senha!");
				FacesUtils.addMessage("j_username", l_msg);
				FacesUtils.addMessage("j_password", l_msg);
				return null;
			} else if (!l_userLogin.getPassword().equals(l_atualUser.getPassword())) {

				FacesMessage l_msg = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_ERROR,
						"Falha ao Efetuar o Login!", "Falha ao Efetuar o Login, "
								+ "Verifique sua Senha, e login digitado!");
				FacesUtils.addMessage("loginFailure", l_msg);
				return null;
			}

			log.info("User: " + l_userLogin);

			setUserLogin(l_userLogin);

			setUserLoged(l_userLogin);

		} catch (Exception _e) {
			log.warn("Problemas ao Efetuar o Login", _e);
			FacesMessage l_msg = FacesMessageUtils.getMessage(FacesMessage.SEVERITY_ERROR, "Falha ao Efetuar o Login!",
					"Falha ao Efetuar o Login, " + "Falha ao Efetuar o Login!");
			FacesUtils.addMessage("loginFailure", l_msg);
			return null;
		}

		fireLogonEvent();

		return ActionLoginManager.loginSucess;
	}

	/**
	 * @param p_userLogin
	 */
	private void setUserLoged(UserLogin p_userLogin) {

		FacesUtils.setSessionMapValue("userLogin", p_userLogin);

		ExternalContext l_externalContext = FacesUtils.getExternalContext();
		Map<String, Object> l_requestMap = l_externalContext.getRequestMap();
		log.info(l_requestMap);

		p_userLogin.setLogged(true);

		l_requestMap.put("j_userlogin", p_userLogin.getUserLogin());
		l_requestMap.put("j_password", p_userLogin.getPassword());

		try {
			getPersonssManager().increaseAccessCount(p_userLogin);
			// getUserManager().logSiteUse(p_userLogin, "Login");
		} catch (PersonsManagerException e) {
			log.warn("Problemas no login: ", e);
		}
	}

	public Action makeLogoff() {
		logoffUser = getUserLogin();

		try {
			getPersonssManager().logSiteUse(logoffUser, "Logoff");
		} catch (PersonsManagerException e) {
			log.warn("Problemas no logoff: ", e);
		}
		setUserLogin(new UserLogin());
		FacesUtils.setSessionMapValue("userLogin", getUserLogin());

		fireLogoffEvent();

		return ActionLoginManager.logoff;
	}

	/**
	 * 
	 */
	private void fireLogoffEvent() {
		ZipperEventListener[] l_listeners = eventListenerList.getListeners(ZipperEventListener.class);
		LoginEvent l_loginEvent = new LoginEvent(new Date(), LoginEvent.USER_LOGOFF, this, this.getUserLogin());
		for (int l_i = 0; l_i < l_listeners.length; l_i++) { 
			l_listeners[l_i].processEvent(l_loginEvent);

		}
	}

	private void fireLogonEvent() {
		ZipperEventListener[] listeners = eventListenerList.getListeners(ZipperEventListener.class);
		
		LoginEvent l_loginEvent = new LoginEvent(new Date(), LoginEvent.USER_LOGIN, this, this.getUserLogin());
		
		for (int l_i = 0; l_i < listeners.length; l_i++) {

			ZipperEventListener l_listener = (ZipperEventListener) listeners[l_i];
			l_listener.processEvent(l_loginEvent);

		}
	}

	/**
	 * @return the usersManager
	 */
	public PersonsManager getPersonssManager() {

		PersonsManager usersManager = ServiceLocator.getService(PersonsManager.class, "remote");

		return usersManager;
	}

	/**
	 * @return the logoffUser
	 */
	public UserLogin getLogoffUser() {
		return logoffUser;
	}

	/**
	 * @param p_logoffUser
	 *            the logoffUser to set
	 */
	public void setLogoffUser(UserLogin p_logoffUser) {
		logoffUser = p_logoffUser;
	}

	/**
	 * @param p_userLogin
	 */
	public UserLogin refreshUserLogin(UserLogin p_userLogin) {
		UserLogin l_userLogin = getPersonssManager().refreshUserLogin(p_userLogin);

		log.info("User Roles: " + l_userLogin.getRoles());
		forceUserLogin(l_userLogin);
		return l_userLogin;
	}

	/**
	 * @return
	 */
	public UserLogin refreshUserLogin() {
		return refreshUserLogin(getUserLogin());
	}

	/**
	 * Define no UserLogin atual o user informado.
	 * 
	 * @param p_user
	 */
	public void setUser(User p_user) {
		getUserLogin().setUser(p_user);
	}

	/**
	 * @param p_menuManager
	 * @param p_userLogin
	 * @param p_userLogoff
	 * @param p_userAddRoles
	 * @param p_userRemoveRoles
	 */
	public void addEventListener(ZipperEventListener p_menuManager) {
		eventListenerList.add(ZipperEventListener.class, p_menuManager);
	}

}
